/*****************************************************************************
 *   Copyright 2005 Tim A Wang
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 ****************************************************************************/
package com.jb.web.bean.inspector;

import java.awt.Component;

import java.beans.BeanDescriptor;
import java.beans.BeanInfo;
import java.beans.FeatureDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.ListIterator;

import javax.swing.table.AbstractTableModel;


public class PropertyTableModel extends AbstractTableModel {
    static final long serialVersionUID = 123123L;

    // Shared instance of a comparator
    static private DescriptorComparator comparator = new DescriptorComparator();
    static final private int NUM_COLUMNS = 2;
    public static final int COL_NAME = 0;
    public static final int COL_VALUE = 1;

    // View options
    public static final int VIEW_ALL = 0;
    public static final int VIEW_STANDARD = 1;
    public static final int VIEW_EXPERT = 2;
    public static final int VIEW_READ_ONLY = 3;
    public static final int VIEW_BOUND = 4;
    public static final int VIEW_CONSTRAINED = 5;
    public static final int VIEW_HIDDEN = 6;
    public static final int VIEW_PREFERRED = 7;

    // Sort options
    public static final int SORT_DEF = 0;
    public static final int SORT_NAME = 1;
    public static final int SORT_TYPE = 2;
    private BeanDescriptor beanDescriptor;
    private BeanInfo info;

    // Cached property editors.
    private Hashtable propEditors;
    private Object bean;
    private PropertyDescriptor[] descriptors;

    public PropertyTableModel() {
        propEditors = new Hashtable();
    }

    public PropertyTableModel(Object bean) {
        this();
        setObject(bean);
    }

    /**
     * Check if given cell is editable
     * @param row table row
     * @param col table column
     */
    public boolean isCellEditable(int row, int col) {
        if ((col == COL_VALUE) && (descriptors != null)) {
            return (descriptors[row].getWriteMethod() == null) ? false : true;
        } else {
            return false;
        }
    }

    /**
     * Get column count (2: name, value)
     */
    public int getColumnCount() {
        return NUM_COLUMNS;
    }

    /**
     * Gets the customizer for the current object.
     * @return New instance of the customizer or null if there isn't a customizer.
     */
    public Component getCustomizer() {
        Component customizer = null;

        if (beanDescriptor != null) {
            Class cls = beanDescriptor.getCustomizerClass();

            if (cls != null) {
                try {
                    customizer = (Component) cls.newInstance();
                } catch (Exception ex) {
                    // XXX - debug
                    System.out.println("PropertyTableModel: Instantiation exception creating Customizer");
                }
            }
        }

        return customizer;
    }

    /**
     * Set the table model to represents the properties of the object.
     */
    public void setObject(Object bean) {
        this.bean = bean;

        try {
            info = Introspector.getBeanInfo(bean.getClass());

            if (info != null) {
                descriptors = info.getPropertyDescriptors();
                beanDescriptor = info.getBeanDescriptor();
                sortTable(SORT_NAME);
            }
        } catch (IntrospectionException e) {
            e.printStackTrace();
        }
    }

    /**
     * Return the current object that is represented by this model.
     */
    public Object getObject() {
        return bean;
    }

    /**
     * Returns the PropertyDescriptor for the row.
     */
    public PropertyDescriptor getPropertyDescriptor(int row) {
        return descriptors[row];
    }

    /**
     * Returns a new instance of the property editor for a given class. If an
     * editor is not specified in the property descriptor then it is looked up
     * in the PropertyEditorManager.
     */
    public PropertyEditor getPropertyEditor(int row) {
        Class cls = descriptors[row].getPropertyEditorClass();

        PropertyEditor editor = null;

        if (cls != null) {
            try {
                editor = (PropertyEditor) cls.newInstance();
            } catch (Exception ex) {
                // XXX - debug
                System.out.println("PropertyTableModel: Instantiation exception creating PropertyEditor");
            }
        } else {
            // Look for a registered editor for this type.
            Class type = getPropertyType(row);

            if (type != null) {
                editor = (PropertyEditor) propEditors.get(type);

                if (editor == null) {
                    // Load a shared instance of the property editor.
                    editor = PropertyEditorManager.findEditor(type);

                    if (editor != null) {
                        propEditors.put(type, editor);
                    }
                }

                if (editor == null) {
                    // Use the editor for Object.class
                    editor = (PropertyEditor) propEditors.get(Object.class);

                    if (editor == null) {
                        editor = PropertyEditorManager.findEditor(Object.class);

                        if (editor != null) {
                            propEditors.put(Object.class, editor);
                        }
                    }
                }
            }
        }

        return editor;
    }

    /**
     * Returns the Java type info for the property at the given row.
     */
    public Class getPropertyType(int row) {
        return descriptors[row].getPropertyType();
    }

    /**
     * Get row count (total number of properties shown)
     */
    public int getRowCount() {
        if (descriptors == null) {
            return 0;
        }

        return descriptors.length;
    }

    /**
     * Set the value of the Values column.
     */
    public void setValueAt(Object value, int row, int column) {
        if ((column != COL_VALUE) || (descriptors == null) || (row > descriptors.length)) {
            return;
        }

        Method setter = descriptors[row].getWriteMethod();

        if (setter != null) {
            try {
                setter.invoke(bean, new Object[] { value });
            } catch (IllegalArgumentException ex) {
                // XXX - handle better
                System.out.println("Setter: " + setter + "\nArgument: " + value.getClass().toString());
                System.out.println("Row: " + row + " Column: " + column);
                ex.printStackTrace();
                System.out.println("\n");
            } catch (IllegalAccessException ex2) {
                // XXX - handle better
                System.out.println("Setter: " + setter + "\nArgument: " + value.getClass().toString());
                System.out.println("Row: " + row + " Column: " + column);
                ex2.printStackTrace();
                System.out.println("\n");
            } catch (InvocationTargetException ex3) {
                // XXX - handle better
                System.out.println("Setter: " + setter + "\nArgument: " + value.getClass().toString());
                System.out.println("Row: " + row + " Column: " + column);
                ex3.printStackTrace();
                System.out.println("\n");
            }
        }
    }

    /**
     * Get text value for cell of table
     * @param row table row
     * @param col table column
     */
    public Object getValueAt(int row, int col) {
        Object value = null;

        if (col == COL_NAME) {
            value = descriptors[row].getDisplayName();
        } else {
            // COL_VALUE is handled        
            Method getter = descriptors[row].getReadMethod();

            if (getter != null) {
                Class[] paramTypes = getter.getParameterTypes();
                Object[] args = new Object[paramTypes.length];

                try {
                    for (int i = 0; i < paramTypes.length; i++) {
                        // XXX - debug
                        System.out.println("\tShouldn't happen! getValueAt getter = " + getter + " parameter = " +
                            paramTypes[i]);
                        args[i] = paramTypes[i].newInstance();
                    }
                } catch (Exception ex) {
                    // XXX - handle better
                    ex.printStackTrace();
                }

                try {
                    value = getter.invoke(bean, args);
                } catch (IllegalArgumentException ex) {
                    // XXX - handle better
                    ex.printStackTrace();
                } catch (IllegalAccessException ex2) {
                    // XXX - handle better
                    ex2.printStackTrace();
                } catch (InvocationTargetException ex3) {
                    // XXX - handle better
                    ex3.printStackTrace();
                } catch (NoSuchMethodError ex4) {
                    // XXX - handle better
                    System.out.println("NoSuchMethodError for method invocation");
                    System.out.println("Bean: " + bean.toString());
                    System.out.println("Getter: " + getter.getName());
                    System.out.println("Getter args: ");

                    for (int i = 0; i < args.length; i++) {
                        System.out.println("\t" + "type: " + paramTypes[i] + " value: " + args[i]);
                    }

                    ex4.printStackTrace();
                }
            }
        }

        return value;
    }

    /**
     * Filters the table to display only properties with specific attributes.
     * @param view The properties to display.
     */
    public void filterTable(int view) {
        if (info == null) {
            return;
        }

        descriptors = info.getPropertyDescriptors();

        // Use collections to filter out unwanted properties
        ArrayList list = new ArrayList();
        list.addAll(Arrays.asList(descriptors));

        ListIterator iterator = list.listIterator();
        PropertyDescriptor desc;

        while (iterator.hasNext()) {
            desc = (PropertyDescriptor) iterator.next();

            switch (view) {
            case VIEW_ALL:

                if (desc.isHidden()) {
                    iterator.remove();
                }

                break;

            case VIEW_STANDARD:

                if ((desc.getWriteMethod() == null) || desc.isExpert() || desc.isHidden()) {
                    iterator.remove();
                }

                break;

            case VIEW_EXPERT:

                if ((desc.getWriteMethod() == null) || !desc.isExpert() || desc.isHidden()) {
                    iterator.remove();
                }

                break;

            case VIEW_READ_ONLY:

                if ((desc.getWriteMethod() != null) || desc.isHidden()) {
                    iterator.remove();
                }

                break;

            case VIEW_HIDDEN:

                if (!desc.isHidden()) {
                    iterator.remove();
                }

                break;

            case VIEW_BOUND:

                if (!desc.isBound() || desc.isHidden()) {
                    iterator.remove();
                }

                break;

            case VIEW_CONSTRAINED:

                if (!desc.isConstrained() || desc.isHidden()) {
                    iterator.remove();
                }

                break;

            case VIEW_PREFERRED:

                if (!desc.isPreferred() || desc.isHidden()) {
                    iterator.remove();
                }

                break;
            }
        }

        descriptors = (PropertyDescriptor[]) list.toArray(new PropertyDescriptor[list.size()]);
        sortTable(SORT_NAME);
    }

    /**
     * Returns a flag indicating if the encapsulated object has a customizer.
     */
    public boolean hasCustomizer() {
        if (beanDescriptor != null) {
            Class cls = beanDescriptor.getCustomizerClass();

            return (cls != null);
        }

        return false;
    }

    /**
     * Sorts the table according to the sort type.
     */
    public void sortTable(int sort) {
        if ((sort == SORT_DEF) || (descriptors == null)) {
            return;
        }

        if (sort == SORT_NAME) {
            Arrays.sort(descriptors, comparator);
        } else {
            Arrays.sort(descriptors, comparator);
        }

        fireTableDataChanged();
    }
}


class DescriptorComparator implements Comparator {
    public int compare(Object o1, Object o2) {
        FeatureDescriptor f1 = (FeatureDescriptor) o1;
        FeatureDescriptor f2 = (FeatureDescriptor) o2;

        return f1.getDisplayName().compareTo(f2.getDisplayName());
    }
}
